﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StableDiffusionTools.Utils
{
    public static class MathUtil
    {
        /// <summary>
        /// The value for which all absolute numbers smaller than are considered equal to zero.
        /// </summary>
        public const float ZeroTolerance = 1e-6f; // Value a 8x higher than 1.19209290E-07F

        /// <summary>
        /// A value specifying the approximation of π which is 180 degrees.
        /// </summary>
        public const float Pi = (float)Math.PI;

        /// <summary>
        /// A value specifying the approximation of 2π which is 360 degrees.
        /// </summary>
        public const float TwoPi = (float)(2 * Math.PI);

        /// <summary>
        /// A value specifying the approximation of π/2 which is 90 degrees.
        /// </summary>
        public const float PiOverTwo = (float)(Math.PI / 2);

        /// <summary>
        /// A value specifying the approximation of π/4 which is 45 degrees.
        /// </summary>
        public const float PiOverFour = (float)(Math.PI / 4);

        //////////////////////////
        //Greatest Common Denominator
        //////////////////////////
        public static int GCD(int a, int b)
        {
            return b == 0 ? Math.Abs(a) : GCD(b, a % b);
        }

        //////////////////////////
        //Clamp
        //////////////////////////
        public static int Clamp(int value, int min, int max)
        {
            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");
            return Math.Min(Math.Max(value, min), max);
        }

        public static double Clamp(double value, double min, double max)
        {
            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");
            return Math.Min(Math.Max(value, min), max);
        }

        public static float Clamp(float value, float min, float max)
        {
            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");
            return Math.Min(Math.Max(value, min), max);
        }

        public static decimal Clamp(decimal value, decimal min, decimal max)
        {
            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");
            return Math.Min(Math.Max(value, min), max);
        }

        //////////////////////////
        //Clamp01
        //////////////////////////
        public static double Clamp01(double value)
        {
            return Math.Min(Math.Max(value, 0.0), 1.0);
        }

        public static float Clamp01(float value)
        {
            return Math.Min(Math.Max(value, 0.0f), 1.0f);
        }

        public static decimal Clamp01(decimal value)
        {
            return Math.Min(Math.Max(value, 0.0m), 1.0m);
        }

        //////////////////////////
        //Map
        //////////////////////////
        public static double Map(int value, int fromStart, int fromEnd, int toStart, int toEnd)
        {
            double scale = (double)(toEnd - toStart) / (fromEnd - fromStart);
            return toStart + ((value - fromStart) * scale);
        }

        //////////////////////////
        //AsymptoticAverage
        //////////////////////////
        public static double AsymptoticAverage(double value, double target, double amount)
        {
            return value + ((target - value) * amount);
        }

        public static float AsymptoticAverage(float value, float target, float amount)
        {
            return value + ((target - value) * amount);
        }

        public static decimal AsymptoticAverage(decimal value, decimal target, decimal amount)
        {
            return value + ((target - value) * amount);
        }

        //////////////////////////
        //Lerp
        //////////////////////////
        public static float Lerp(float firstFloat, float secondFloat, float value)
        {
            return firstFloat * (1 - value) + secondFloat * value;
        }

        //////////////////////////
        //Wrap
        //////////////////////////
        public static int Wrap(int value, int min, int max)
        {
            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");

            int range_size = max - min + 1;

            if (value < min)
                value += range_size * ((min - value) / range_size + 1);

            return min + (value - min) % range_size;
        }

        public static float Wrap(float value, float min, float max)
        {
            if (NearEqual(min, max)) return min;

            double mind = min;
            double maxd = max;
            double valued = value;

            if (mind > maxd)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");

            var range_size = maxd - mind;
            return (float)(mind + (valued - mind) - (range_size * Math.Floor((valued - mind) / range_size)));
        }

        public static double Wrap(double value, double min, double max)
        {
            if (NearEqual(min, max)) return min;


            if (min > max)
                throw new ArgumentException(string.Format("min {0} should be less than or equal to max {1}", min, max), "min");

            var range_size = max - min;
            return (min + (value - min) - (range_size * Math.Floor((value - min) / range_size)));
        }

        public unsafe static bool NearEqual(float a, float b)
        {
            if (IsZero(a - b))
                return true;

            int aInt = *(int*)&a;
            int bInt = *(int*)&b;

            if ((aInt < 0) != (bInt < 0))
                return false;

            int ulp = Math.Abs(aInt - bInt);

            const int maxUlp = 4;
            return (ulp <= maxUlp);
        }

        public unsafe static bool NearEqual(double a, double b)
        {
            if (IsZero(a - b))
                return true;

            long aInt = *(long*)&a;
            long bInt = *(long*)&b;

            if ((aInt < 0) != (bInt < 0))
                return false;

            long ulp = Math.Abs(aInt - bInt);

            const int maxUlp = 4;
            return (ulp <= maxUlp);
        }

        public static bool IsZero(float a)
        {
            return Math.Abs(a) < ZeroTolerance;
        }

        public static bool IsZero(double a)
        {
            return Math.Abs(a) < ZeroTolerance;
        }

        public static bool IsOne(float a)
        {
            return IsZero(a - 1.0f);
        }

        public static bool IsOne(double a)
        {
            return IsZero(a - 1.0f);
        }
    }
}
